package ru.dc.object.commands;


import org.apache.commons.lang.StringUtils;
import ru.dc.object.SearchConstants;


/**
 * <h3>Syntax</h3>
 * <p/>
 * Active user
 * <pre>
 * $Search &lt;ip>:&lt;port> &lt;searchstring>
 * </pre>
 * <p/>
 * Passive user
 * <pre>
 * $Search Hub:&lt;requestornick> &lt;searchstring>
 * </pre>
 * <p/>
 * <p/>
 * <p/>
 * <h3>Description</h3>
 * <p>This command is used to initiate a search. The format used differs based on whether the client is
 * in active mode or passive mode. <p>
 * <p/>
 * <p>Active User Request:</p>
 * <p/>
 * <ul>
 * <li>&lt;ip> is this client's own IP address.</li>
 * <li>&lt;port> is a UDP port on which the client is listening for responses.</li>
 * </ul>
 * <p/>
 * <p>Passive User Request:</p>
 * <p/>
 * <p/>
 * <ul>
 * <li>&lt;requestornick> is the Nick of the Passive User doing the Search.</li>
 * <li>&lt;searchstring> is a string describing the file the client is searching for.
 * It is made up of a question mark ('?') delimited string as follows:
 * <pre>
 * &lt;sizerestricted>?&lt;ismaxsize>?&lt;size>?&lt;datatype>?&lt;searchpattern>
 * </pre>
 * </li>
 * <p/>
 * <li>&lt;sizerestricted> is 'T' if the search should be restricted to files of a minimum or maximum size, otherwise 'F'.</li>
 * <li>&lt;ismaxsize> is 'F' if &lt;sizerestricted> is 'F' or if the size restriction places an lower limit on file size, otherwise 'T'.</li>
 * <li>&lt;size> is the minimum or maximum size of the file to report (according to &lt;ismaxsize>) if &lt;sizerestricted> is 'T', otherwise 0.</li>
 * <li>&lt;datatype> restricts the search to files of a particular type. It is an integer selected from:
 * <ul>
 * <li>1 for any file type </li>
 * <li>2 for audio files ("mp3", "mp2", "wav", "au", "rm", "mid", "sm")</li>
 * <li>3 for compressed files ("zip", "arj", "rar", "lzh", "gz", "z", "arc", "pak")</li>
 * <li>4 for documents ("doc", "txt", "wri", "pdf", "ps", "tex")</li>
 * <li>5 for executables ("pm", "exe", "bat", "com")</li>
 * <li>6 for pictures ("gif", "jpg", "jpeg", "bmp", "pcx", "png", "wmf", "psd")</li>
 * <li>7 for video ("mpg", "mpeg", "avi", "asf", "mov")</li>
 * <li>8 for folders</li>
 * <li>9 for TTHSearch</li>
 * </ul>
 * </li>
 * <li>&lt;searchpattern> is used by other users to determine if any files match.
 * <li>if is TTHSearch then &lt;searchpattern> is TTH:_file_tth_
 * </ul>
 * <p/>
 * <h3>Behavior of DC++:</h3>
 * <ul>
 * <li> Spaces are replaced by '$'.</li>
 * <li>As with all NMDC messages, '$' and '|' are escaped with "$" and "|", with '&' being further replaced with "&"</li>
 * </ul>
 * <p/>
 * <p/>
 * <p/>
 * <p/>
 * <p>
 * The server must forward this message unmodified to all the other users. Every other user
 * with one or more matching files must send a UDP packet to &lt;ip>:&lt;port> for Active requests or a
 * reply to the Hub for Passive requests.
 * </p>
 * <p/>
 * <p>
 * See the $SR command for details of response format.
 * </p>
 * <p/>
 * <h3>Examples</h3>
 * <pre>
 * $Search 64.78.55.32:412 T?T?500000?1?Gentoo$2005
 * $Search Hub:SomeNick T?T?500000?1?Gentoo$2005
 * $Search 17.12.85.3:3746 F?T?0?9?TTH:TO32WPD6AQE7VA7654HEAM5GKFQGIL7F2BEKFNA
 * $Search Hub:SomeNick F?T?0?9?TTH:TO32WPD6AQE7VA7654HEAM5GKFQGIL7F2BEKFNA
 * </pre>
 *
 * <a href="http://www.teamfair.info/wiki/index.php?title=$Search">http://www.teamfair.info/wiki/index.php?title=$Search</a>
 */
public class Search extends Command implements SearchConstants {

    private String address;
    private int port;
    private String requestorNick;
    private boolean active;
    private boolean sizeRestricted;
    private boolean maxSize;
    private long size;
    private SearchType dataType = SearchType.ANY;
    private String searchPattern;
    private static final String PASSIVE_SEARCH_MARKER = "Hub:";

    @Override
    public String generateCommand() {

        String searchPatternStr;
        if (getDataType() == SearchType.TTH) {
            searchPatternStr = "TTH:" + getSearchPattern();
        } else {
            searchPatternStr = getSearchPattern().replaceAll(" ", "$");
        }

        return getActualCommandName() + " " +
                (isActive() ? getAddress() + ":" + getPort() : PASSIVE_SEARCH_MARKER + getRequestorNick()) + " " +
                (isSizeRestricted() ? "T" : "F") + "?" +
                (isMaxSize() && isSizeRestricted() ? "T" : "F") + "?" +
                getSize() + "?" +
                getDataType().getId() + "?" +
                searchPatternStr;
    }

    @Override
    public String getActualCommandName() {
        return "$Search";
    }

    @Override
    public void parseCommand() {
        final String cmd = getCommand();
        int len = cmd.length();

        int startAddrIndex = 8;//length('$Search')
        int endAddrIndex = cmd.indexOf(' ', startAddrIndex);
        String addr = cmd.substring(startAddrIndex, endAddrIndex);
        int dotIndex = addr.indexOf(':');
        String ip = addr.substring(0, dotIndex);
        String port = addr.substring(dotIndex + 1);
        if ("Hub".equals(ip)) {
            setActive(false);
            setRequestorNick(port);
        } else {
            setActive(true);
            setAddress(ip);
            setPort(!StringUtils.isEmpty(port) && StringUtils.isNumeric(port) ? new Integer(port) : 0);
        }


        int startSizeRestricted = endAddrIndex + 1;
        setSizeRestricted(cmd.charAt(startSizeRestricted) == 'T');
        setMaxSize(cmd.charAt(startSizeRestricted + 2) == 'T');
        int startSize = startSizeRestricted + 4;
        int endSize = cmd.indexOf('?', startSize);
        setSize(new Long(cmd.substring(startSize, endSize)));
        int searchType = new Integer(cmd.substring(endSize + 1, endSize + 2));
        switch (searchType) {
            case 1:
                setDataType(SearchType.ANY);
                break;
            case 2:
                setDataType(SearchType.AUDIO);
                break;
            case 3:
                setDataType(SearchType.ARCHIVE);
                break;
            case 4:
                setDataType(SearchType.DOCUMENTS);
                break;
            case 5:
                setDataType(SearchType.EXECUTABLES);
                break;
            case 6:
                setDataType(SearchType.PICTURES);
                break;
            case 7:
                setDataType(SearchType.VIDEO);
                break;
            case 8:
                setDataType(SearchType.FOLDERS);
                break;
            case 9:
                setDataType(SearchType.TTH);
                break;
        }


        if (len > endSize + 3) {
            String searchPattern = cmd.substring(endSize + 3);
            if (getDataType() != SearchType.TTH) {
                setSearchPattern(searchPattern.replaceAll("\\$", " "));
            } else {
                setSearchPattern(searchPattern.substring(4));
            }
        }

    }


    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public SearchType getDataType() {
        return dataType;
    }

    public void setDataType(SearchType dataType) {
        this.dataType = dataType;
    }

    public boolean isMaxSize() {
        return maxSize;
    }

    public void setMaxSize(boolean maxSize) {
        this.maxSize = maxSize;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getRequestorNick() {
        return requestorNick;
    }

    public void setRequestorNick(String requestorNick) {
        this.requestorNick = requestorNick;
    }

    public String getSearchPattern() {
        return searchPattern;
    }

    public void setSearchPattern(String searchPattern) {
        this.searchPattern = searchPattern;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public boolean isSizeRestricted() {
        return sizeRestricted;
    }

    public void setSizeRestricted(boolean sizeRestricted) {
        this.sizeRestricted = sizeRestricted;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

}


